`timescale 1ns / 1ps
//********************************************************************** 
// -------------------------------------------------------------------
// Disclaimers
// -------------------------------------------------------------------
// When you use this source file, please note that the author assumes 
// no legal or non-legal responsibility for any consequences of your use 
// of it including, but not limited to, the specific use of the code or 
// liability to you for infringement of any patent, copyright or other 
// intellectual property rights.
// If you do not agree to the terms, please do not use the file and 
// delete the file promptly.
// -------------------------------------------------------------------
// Copyright Notice
// -------------------------------------------------------------------
// This source file may be used for personal study, provided that this 
// copyright notice is not removed from the file.
// and that derivative works of this source file contain the original 
// copyright notice and related disclaimers.
// For commercial use, please contact the author for authorization, 
// otherwise the author reserves all rights.
// ------------------------------------------------------------------- 
// Author: Geeker_FPGA 
// Email:geeker_fpga@uisrc.com 
// Date:2022/03 
// Description: 
// top_tb
//
// 
// Web:http://www.uisrc.com
//------------------------------------------------------------------- 
//*********************************************************************/
module mdio_top(
		input   			clk,
		input   			rst_n,
		input          		mdio_en,
		inout   			mdio,
		output wire			mdc,
        
        // 使用led测试mdio是否有返回
		output wire 		led
    );
    
    
localparam		PHY_ADDRESS = 5'b00011;


reg  [4:0]   register_address;
reg  [3:0]   reg_index;

reg          mdio_write_en;
reg  [15:0]  write_register_data; //synthesis keep
wire         mdio_write_done;

reg          mdio_read_en;
wire [15:0]  read_register_data; //synthesis keep
wire         mdio_read_done;
wire 		 key_pos;
reg 		 mdc_clk;

// mdc_clk 时钟信号和 clk 频率相差一半
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		mdc_clk <= 0;
	else
		mdc_clk <= ~mdc_clk;
end


// 消抖模块，mdio_en 作为外部按键 或者 开关状态
// key_pos 为稳定的输出
debounce 
#(
	.CLK_FREQ (32'd25_000_000	)
)u_key_r
(
	.clk	  (clk				),
	.rst_n    (rst_n			),
	.key	  (mdio_en			),
	.key_pos  (key_pos			)
); 

reg  [4:0]   phy_address;			// b011
reg          phy_index;

reg  [31:0]  cnt;
reg  [3:0]   STATE;

reg  [1:0]   PHY_STATE;
reg			 led_r						= 1'b0;

assign led = led_r;


// 以下实现 MDIO 状态机
localparam   IDLE 						= 4'd0;
localparam   READ_FIRST_STATUS 		    = 4'd1;
localparam   WAIT_READ_FIRST_DONE 	    = 4'd2;
localparam   WRITE_PHY_PAGE 			= 4'd3;
localparam   READ_PHY_STATUS 	        = 4'd4;
localparam   WAIT_PHY_STATUS 			= 4'd5;


// mdio 协议 处理模块
mdio_control_rw ux_mdio_control 
(
    .clk					    (mdc_clk			), 
    .rst_n						(rst_n				), 
    .write_start				(mdio_write_en		), 
    .read_start				    (mdio_read_en		), 
	.busy				        (					),
    .phy_address				(phy_address		), 
    .register_address			(register_address	), 
    .write_done			        (mdio_write_done	), 
    .write_register_data		(write_register_data), 
    .read_done				    (mdio_read_done		), 
    .read_register_data			(read_register_data	), 
    .mdio						(mdio				), 
    .mdc						(mdc				)
);


// 
always@(posedge mdc_clk or negedge rst_n)
	begin
		if(!rst_n) 
		begin
			mdio_write_en 		<= 1'b0;
			mdio_read_en 		<= 1'b0;
			phy_address 		<= 5'd0;
			phy_index 			<= 1'b0;
			register_address 	<= 5'd0;
			reg_index           <= 4'd0;
			write_register_data <= 16'd0;
			cnt 				<= 32'd0;
			STATE 				<= IDLE;
		end
		else 
		begin
			case(STATE)

				// 空闲状态
				IDLE:
					begin
						// 如果按键被按下
						if(key_pos)
							// 进入读取状态
							STATE <= READ_FIRST_STATUS;
						else
							STATE <= IDLE;
					end
				READ_FIRST_STATUS:
					begin
						mdio_read_en     <= 1'b1;
						phy_address      <= PHY_ADDRESS; 
						register_address <= 5'h00;
						STATE            <= WAIT_READ_FIRST_DONE;
					end	
				WAIT_READ_FIRST_DONE:
					begin
						if(mdio_read_done) 
						begin
							mdio_read_en  <= 1'b0;
							STATE <= WRITE_PHY_PAGE; 
						end
						else 
							STATE <= WAIT_READ_FIRST_DONE;
					end
				WRITE_PHY_PAGE:
					begin					   
						register_address    <= 5'h00;
						phy_address         <= PHY_ADDRESS;
						write_register_data <= 16'b0010_0001_0100_0000;
						if(mdio_write_done) 
						begin
							mdio_write_en   <= 1'b0;
							STATE           <= READ_PHY_STATUS;
						end
						else 
						begin
							mdio_write_en <= 1'b1;
							STATE         <= WRITE_PHY_PAGE;
						end
					end
				READ_PHY_STATUS:
					begin
						mdio_read_en     <= 1'b1;
						phy_address      <= PHY_ADDRESS;
						register_address <= 5'd0;
						STATE            <= WAIT_PHY_STATUS;
					end					
				WAIT_PHY_STATUS:
					begin
						if(mdio_read_done) 
						begin
							mdio_read_en 	<= 1'b0;						
							if(read_register_data == 16'b0010_0001_0100_0000)
							begin
								led_r	  <= ~led_r;
								STATE     <= IDLE; 
							end
						end
						else 
							STATE <= WAIT_PHY_STATUS;
					end	
				default: STATE <= IDLE;
			endcase		
		end
	end

endmodule


// 按键消抖模块
module debounce 
#(
parameter  CLK_FREQ  =  32'd25_000_000
)
(
	input	wire clk,
	input	wire rst_n,	
	input   wire key,
	output	reg  key_pos
); 

localparam TIME 		= 1000_000_000/CLK_FREQ;
localparam DELAY_NUM	= 20_000_000/TIME;

reg [31:0] 	delay_cnt;
reg  		key_ff0;                
reg 	 	key_ff1;  
reg 	 	key_pos_d;
reg 	 	key_pos_d1;


// 这里是一个延迟操作 ，延迟一共 40ns * 20_000_000 = 0.8 s
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
	begin
		delay_cnt <= 32'd0;
	end
    else if(delay_cnt == DELAY_NUM)
	begin
		delay_cnt <= 32'd0;
	end
	else
	begin
		delay_cnt <= delay_cnt + 32'd1;
	end
end



always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
	begin
		key_ff0 <= 1'b1;
		key_ff1 <= 1'b1;	
	end

	// 0.8 s 会触发一次
	else if(delay_cnt == DELAY_NUM)
	begin
		key_ff0 <= key;
		key_ff1 <= key_ff0;
	end

	// 其他时间保持不变
	else
	begin
		key_ff0 <= key_ff0;
		key_ff1 <= key_ff1;
	end	
end

always@(posedge clk)
begin
	// 按键被释放的瞬间 key_pos_d 为 1
	key_pos_d <= ~key_ff1 & key_ff0 ; 
	key_pos_d1<= key_pos_d;
	key_pos   <= ~key_pos_d1 & key_pos_d;
	// key_neg   <= ~key_pos_d & key_pos_d1;
end

endmodule